//========================================================================
//  SE5 AI State Change - Captain Kwok's Balance Mod v1.20+
//========================================================================
//
// 1. Determine our current state
// 2. Note priority systems for attack and defending
//
// Balance Mod Changes:
// --------------------
// v1.20 Changed - Incorporated AI empire strategy modifiers in state identification
// v1.19 Changed - Modified conditions for exiting Explore & Expand state
//       Fixed   - The AI was sometimes failing to identify a disconnected state (v1.19c)
// v1.15 Changed - Updated the criteria for determining if the AI is disconnected
//       Fixed   - The AI was still exiting the Explore and Expand state too early
//       Fixed   - Sometimes the AI would get stuck in a state that it shouldn't be in
// v1.13 Changed - Revised the conditions that determine if the AI is in a disconnected state
// v1.12 Changed - Fx[Compute_Attack_Systems] now returns a list of systems to attack
//       Changed - Fx[Compute_Buildup_Systems] returns a list of corresponding systems to attack systems
//       Removed - Fx[Should_We_Continue_To_Attack]
//       Removed - Fx[Is_Good_System_To_Attack]
//       Fixed   - The AI was exiting the Explore & Expand state earlier than it should in some cases
// v1.10 Fixed   - Error in Fx[Is_Good_System_To_Attack]
//       Changed - Moved Fx[Compute_State_Modifier] to Script_AI_Enemy_Analysis.txt
//       Fixed   - The AI would sometimes drop out of Expand & Explore state too early
// v1.06 Added   - New Fx[Compute_AI_State_Modifier] to alter the AI's choice of entering defense or attacking state
// v1.03 Changed - Tweaked attack conditions
// v1.02 Changed - AI will sometimes launch an attack even if there are still unexplorer/uncolonized systems
// v0.92 Changed - AI will continue to explore even if 1 or fewer races are encountered
//
// Balance Mod To-do list:
// -----------------------
// - Nothing pending
//
// Script Function Requests:
// -------------------------
// - None

//------------------------------------------------------------------------
// Forward Declarations
//------------------------------------------------------------------------

deffunc

function Determine_Overall_State returns boolean
params
end

function Are_We_Disconnected returns boolean
params
end

function Should_We_Continue_To_Explore returns boolean
params
end

function Should_We_Defend returns boolean
params
end

function Should_We_Attack returns boolean
params
end

function Compute_Defend_Systems returns boolean
params
end

function Compute_Attack_Systems returns boolean
params
end

function Compute_Attack_Buildup_System returns long
params
  atk_sys:                   long
end

enddeffunc

//------------------------------------------------------------------------
// Global Variables
//------------------------------------------------------------------------

globalvars

  gbl_ai_state_date_entered_current_state:       long

  gbl_lst_ai_state_attack_systems:               longlist
  gbl_lst_ai_state_buildup_systems:              longlist
  gbl_lst_ai_state_defend_systems:               longlist

endglobalvars

//------------------------------------------------------------------------
// AI_StateChange
//------------------------------------------------------------------------
function AI_StateChange returns boolean
begin

  // Determine our current state
  call Determine_Overall_State()

end

//------------------------------------------------------------------------
// Determine_Overall_State
//------------------------------------------------------------------------
function Determine_Overall_State returns boolean
vars
  cur_ai_state:              long
  new_ai_state:              long := 0
  new_ai_state_name:         string := ""
begin

  // Get our current state
  set cur_ai_state := Sys_Get_AI_State(sys_long_Player_ID)
  set gbl_ai_state_date_entered_current_state := Sys_Get_AI_Storage_Long(sys_long_Player_ID, 1, 2)

  // If we have no state, start in Explore and Expand
  if (cur_ai_state = 0) then
    set cur_ai_state := AI_STATE_EXPLORE_AND_EXPAND
    set new_ai_state := AI_STATE_EXPLORE_AND_EXPAND
  endif

  // Debug output
  call Sys_Debug_Print("State","----------")
  call Sys_Debug_Print("State", "AI State:")

  // Determine are systems to attack
  // Debug output
  call Sys_Debug_Print("State", "  - Attack Systems:")
  call Compute_Attack_Systems()

  // Determine our systems to defend
  // Debug output
  call Sys_Debug_Print("State", "  - Defense Systems:")
  call Compute_Defend_Systems()

  // Debug output
  call Sys_Debug_Print("State", "  - Current State:")

  // Determine if we should enter a new state
  case (cur_ai_state)
    AI_STATE_EXPLORE_AND_EXPAND:
      call Sys_Debug_Print("State", "    - Explore & Expand")
      // Are there still systems to explore?
      if (not Should_We_Continue_To_Explore()) then
        // Are we disconnected?
        if Are_We_Disconnected() then
          set new_ai_state := AI_STATE_NOT_CONNECTED
        else
          set new_ai_state := AI_STATE_INFRASTRUCTURE
        endif
      endif
    AI_STATE_INFRASTRUCTURE:
      call Sys_Debug_Print("State", "    - Infrastructure")
      // Should we defend?
      if Should_We_Defend() then
        set new_ai_state := AI_STATE_DEFEND
      endif
      // Should we attack?
      if (new_ai_state = 0) then
        if Should_We_Attack() then
          set new_ai_state := AI_STATE_ATTACK
        endif
      endif
      // Are we disconnected?
      if (new_ai_state = 0) then
        if Are_We_Disconnected() then
          set new_ai_state := AI_STATE_NOT_CONNECTED
        endif
      endif
    AI_STATE_ATTACK:
      call Sys_Debug_Print("State", "    - Attack")
      // Should we enter infrastructure state?
      if not Should_We_Attack() then
        set new_ai_state := AI_STATE_INFRASTRUCTURE
      endif
    AI_STATE_DEFEND:
      call Sys_Debug_Print("State", "    - Defend")
      // Should we enter infrastructure state?
      if not Should_We_Defend() then
        set new_ai_state := AI_STATE_INFRASTRUCTURE
      endif
    AI_STATE_NOT_CONNECTED:
      call Sys_Debug_Print("State", "    - Not Connected")
      // If we are connected, then enter infrastructure state
      if not Are_We_Disconnected() then
        set new_ai_state := AI_STATE_INFRASTRUCTURE
      endif
  endcase

  // Set our new state name
  case (new_ai_state)
    AI_STATE_EXPLORE_AND_EXPAND:
      set new_ai_state_name := "Explore & Expand"
    AI_STATE_INFRASTRUCTURE:
      set new_ai_state_name := "Infrastructure"
    AI_STATE_ATTACK:
      set new_ai_state_name := "Attack"
    AI_STATE_DEFEND:
      set new_ai_state_name := "Defend"
    AI_STATE_NOT_CONNECTED:
      set new_ai_state_name := "Not Connected"
  endcase

  if (new_ai_state > 0) then
    call Sys_Set_AI_State(sys_long_Player_ID, new_ai_state, new_ai_state_name)
    call Sys_Debug_Print("State", "    - Entering new state " + new_ai_state_name)
    set gbl_ai_state_date_entered_current_state := sys_long_Game_Date
  endif

  // Remember the date when we entered our current state and/or an attack state
  call Sys_Set_AI_Storage_Long(sys_long_Player_ID, 1, 2, gbl_ai_state_date_entered_current_state)

  return TRUE
end

//------------------------------------------------------------------------
// Should_We_Continue_To_Explore
//------------------------------------------------------------------------
function Should_We_Continue_To_Explore returns boolean
params
vars
  cont_to_explore:           boolean := FALSE
begin

  // If we are alone, continue exploring until we have no systems left to explore or survey
  if (not bool_Race_Is_Not_Alone) then
    if (lst_AI_Survey_System.count() + lst_AI_Explore_System.count() > 0) then
      set cont_to_explore := TRUE
    else
      set cont_to_explore := FALSE
    endif
  endif

  return cont_to_explore
end

//------------------------------------------------------------------------
// Should_We_Defend
//------------------------------------------------------------------------
function Should_We_Defend returns boolean
params
vars
  def_sys_count:             long
  defend_state:              boolean := FALSE
begin

  // How many defense systems do we have?
  set def_sys_count := gbl_lst_ai_state_defend_systems.count()

  // Set our state to defend if our strategy is defend and we have systems to protect
  if (bool_AI_Strategy_Defend) or (bool_AI_Strategy_Last_Stand) then
    if (def_sys_count > 0) then
      set defend_state := TRUE
    endif
  endif

  return defend_state
end

//------------------------------------------------------------------------
// Should_We_Attack
//------------------------------------------------------------------------
function Should_We_Attack returns boolean
params
vars
  atk_sys_count:             long
  attack_state:              boolean := FALSE
begin

  // How many attack systems do we have?
  set atk_sys_count := gbl_lst_ai_state_attack_systems.count()

  // Set our state to attack if our strategy is attack and we have systems to target
  if (bool_AI_Strategy_Attack) or (bool_AI_Strategy_Flame_Out) then
    if (atk_sys_count > 0) then
      set attack_state := TRUE
    endif
  endif

  return attack_state
end

//------------------------------------------------------------------------
// Compute_Defend_Systems
//------------------------------------------------------------------------
function Compute_Defend_Systems returns boolean
params
vars
  list_count:                long
  list_index:                long
  def_sys:                   long
  def_sys_name:              string
begin

  // Add our defense locations as systems to defend
  set list_count := lst_AI_Defense_Location_System.count()

  if (list_count > 0) then
    for list_index := 1 to list_count do
      set def_sys := lst_AI_Defense_Location_System.get(list_index)
      if (gbl_lst_ai_state_defend_systems.indexof(def_sys) <= 0) then
        if (gbl_lst_ai_state_defend_systems.count() < lng_Max_Defense_Systems) then
          call gbl_lst_ai_state_defend_systems.add(def_sys)
          set def_sys_name := Sys_Get_Solar_System_Name(def_sys)
          // Debug output
          call Sys_Debug_Print("State", "    - " + def_sys_name + " System")
        endif
      endif
    endfor
  else
    // Debug output
    call Sys_Debug_Print("State", "    - None")
  endif

  return TRUE
end

//------------------------------------------------------------------------
// Compute_Attack_Systems
//------------------------------------------------------------------------
function Compute_Attack_Systems returns boolean
params
vars
  list_count:                long
  list_index:                long
  atk_sys:                   long
  atk_sys_owner:             long
  buildup_sys:               long
  priority_level:            long := 4
  atk_sys_name:              string
  buildup_sys_name:          string
begin

  loop
    set priority_level := priority_level - 1
    set list_count := lst_AI_Attack_Location_System.count()
    if (list_count > 0) then
      for list_index := 1 to list_count do
        set atk_sys := lst_AI_Attack_Location_System.get(list_index)
        set atk_sys_owner := lst_AI_Attack_Location_Owner.get(list_index)
        if (lst_Politics_Target_Player_Priority.get(atk_sys_owner) >= priority_level) then
          if (gbl_lst_ai_state_attack_systems.indexof(atk_sys) <= 0) then
            if (gbl_lst_ai_state_attack_systems.count() < lng_Max_Attack_Systems) then
              set buildup_sys := Compute_Attack_Buildup_System(atk_sys)
              call gbl_lst_ai_state_attack_systems.add(atk_sys)
              call gbl_lst_ai_state_buildup_systems.add(buildup_sys)
              set atk_sys_name := Sys_Get_Solar_System_Name(atk_sys)
              set buildup_sys_name := Sys_Get_Solar_System_Name(buildup_sys)
              // Debug Output
              call Sys_Debug_Print("State", "    - " + atk_sys_name + " System and staging in " + buildup_sys_name + " System")
            endif
          endif
        endif
      endfor
    endif
    exitwhen (priority_level <= 0)
  endloop

  // Should we start attacking?
  if (gbl_lst_ai_state_attack_systems.count() > 0) then
    return TRUE
  else
    return FALSE
    // Debug output
    call Sys_Debug_Print("State", "    - There are no good systems to attack at this time")
  endif

end

//------------------------------------------------------------------------
// Compute_Attack_Buildup_System
//------------------------------------------------------------------------
function Compute_Attack_Buildup_System returns long
params
  atk_sys:                   long
vars
  sys_count:                 long
  sys_index:                 long
  buildup_sys:               long := 0
  this_dist:                 long
  nearest_dist:              long := 999999
begin

  // Find our closest colony system to the targeted attack system
  if (atk_sys > 0) then
    set sys_count := Sys_Get_Number_Of_Solar_Systems()
    if (sys_count > 0) then
      for sys_index := 1 to sys_count do
        if (sys_index <> atk_sys) then
          if Sys_Player_Has_Colony_In_System(sys_long_Player_ID, sys_index) then
            set this_dist := Sys_Get_System_Distance_Between_Solar_Systems(sys_index, atk_sys)
            if (this_dist < nearest_dist) then
              set nearest_dist := this_dist
              set buildup_sys := sys_index
            endif
          endif
        endif
      endfor
    endif
  endif

  return buildup_sys
end

//------------------------------------------------------------------------
// Are_We_Disconnected
//------------------------------------------------------------------------
function Are_We_Disconnected returns boolean
params
vars
  home_sys:                  long
  sys_count:                 long
  sys_index:                 long
  dist_between_sys:          long
  num_connected_sys:         long
  min_connected_sys:         long
  num_colonizable_planets:   long
  continue_check:            boolean := FALSE
  disconnected_state:        boolean := TRUE
begin

  // We are not in a disconnected state if there are systems to explore or survey
  if (lst_AI_Explore_System.count() > 0) or (lst_AI_Survey_System.count() > 0) then
    set disconnected_state := FALSE
  endif

  // We are not in a disconnected state if we have the technology to make warp points
  if Is_Component_Available("Gravitational Quantum Resonator") then
    set disconnected_state := FALSE
    set continue_check := TRUE
  endif

  // Note our home system
  set home_sys := Sys_Get_Empire_Home_System(sys_long_Player_ID)

  // Get number of solar systems
  set sys_count := Sys_Get_Number_Of_Solar_Systems()

  // Starting with our home system, see how many systems are ultimately connected to it
  if (disconnected_state) or (continue_check) then
    if (sys_count > 0) then
      for sys_index := 1 to sys_count do
        if (sys_index <> home_sys) then
          set dist_between_sys := Sys_Get_System_Distance_Between_Solar_Systems(home_sys, sys_index)
          // Identify systems that are connected either directly or indirectly to our home system
          if (dist_between_sys < 999) then
            set num_connected_sys := num_connected_sys + 1
            // Only count colonizable planets that are not in enemy systems
            if (not Does_Enemy_Have_Colony_In_System(sys_index)) then
              set num_colonizable_planets := num_colonizable_planets + Sys_Get_Solar_System_Colonizable_Planets_Count(sys_long_Player_ID, sys_index)
            endif
          endif
        endif
      endfor
    endif

    // Increase the minimum number of connected systems that are acceptable if we are low on planets
    if (num_colonizable_planets <= 5) then
      set min_connected_sys := Sys_Trunc(sys_count ^ 0.5) * 2
    else
      set min_connected_sys := Sys_Trunc(sys_count ^ 0.5)
    endif

    // Don't consider ourselves disconnected if we have a pocket of systems
    if (num_connected_sys >= min_connected_sys) then
      set disconnected_state := FALSE
    else
      // Note we have generally poor connectivity to the rest of the quadrant
      set bool_Race_Limited_Connectivity := TRUE
    endif
  endif

  return disconnected_state
end

//------------------------------------------------------------------------